package top.ninwoo.edgecenter.service.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import top.ninwoo.common.entity.ClusterConfig;
import top.ninwoo.common.entity.NetworkTopology;
import top.ninwoo.edgecenter.service.ClusterService;
import top.ninwoo.edgecenter.service.IpService;
import top.ninwoo.edgecenter.service.TopologyService;
import top.ninwoo.utils.service.IptablesService;
import top.ninwoo.utils.service.OVSService;
import top.ninwoo.utils.service.OvsDockerService;

import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author joliu
 * @Description 这里描述的物理连接的拓扑关系
 * @Date　Create in 上午11:16 2019/11/7
 */
@Service
public class TopologyServiceImpl implements TopologyService {
    private final static Logger LOG = LoggerFactory.getLogger(TopologyServiceImpl.class);
    // 用于存储集群的拓扑
    private ConcurrentHashMap<Long, NetworkTopology> clustersTopo = new ConcurrentHashMap<>();

    @Value("${bs.sdn-controller.host}")
    private String host;
    @Value("${bs.sdn-controller.port}")
    private int port;

    @Autowired
    OVSService ovsService;

    @Autowired
    OvsDockerService ovsDockerService;

    // 这个服务接口的具体功能应该下沉
    @Autowired
    ClusterService clusterService;

    @Autowired
    IpService ipService;

    @Autowired
    IptablesService iptablesService;

    @Override
    public void initTopo(ClusterConfig clusterConfig) {
        // 获取topology
        createTopo(clusterConfig.getId(), clusterConfig.getTopology());
    }

    @Override
    public void createTopo(long clusterId, NetworkTopology topology) {
        if(topology == null) {
            LOG.warn("集群[" + clusterId + "]未设置网络");
            return;
        }

        // 校验信息
        String[] cNames = topology.getAppNames();
        int[][] topo = topology.getTopology();
        if(cNames == null || cNames.length <= 1) {
            return;
        }

        // 这里声明：cNames中以br:开头的名称为ovs虚拟交换机
        for (int i = 1; i < cNames.length; i++) {
            for (int j = 0; j < i; j++) {
                // 判断每一个节点的连接状态
                if(topo[i][j] == 1) {
                    // 如果存在连接，则创建连接
                    addLink(clusterId, cNames[i], cNames[j]);
                }
            }
        }
        clustersTopo.put(clusterId, topology);
    }

    public void addLink(long clusterId, String appName1, String appName2) {
        int flag = 0;
        // 校验 参数是否合法
        // step1: 判断appNames中是否存在ovs虚拟交换机
        if(appName1.startsWith("br:")) {
            // flag计数器加1
            flag++;
            // 创建对应的虚拟交换机
            // 在创建前需要检查，是否已经创建过对应的交换机
            if(!appName1.contains("remote") && !ovsService.isBridge(appName1.substring(3))) {
                LOG.info("创建虚拟交换机:{}", appName1.substring(3));
                ovsService.addBridge(appName1.substring(3));
                // todo 设置controller
                ovsService.setController(appName1.substring(3), host, port);
            }
            //LOG.info("Ovs Bridge[" + appName1 + "] is normal!");
        }

        if(appName2.startsWith("br:")) {
            flag++;
            // 创建对应的虚拟交换机
            if(!appName2.contains("remote") && !ovsService.isBridge(appName2.substring(3))) {
                LOG.info("创建虚拟交换机:{}", appName2.substring(3));
                ovsService.addBridge(appName2.substring(3));
                ovsService.setController(appName2.substring(3), host, port);
            }
        }

        // 如果两个都是虚拟交换机，则需要创建交换机连接
        if(flag == 2) {
            // 校验是否是远端的交换机
            // 远端交换机输入样例：br:remote:testBr:192.168.31.111
            String remoteIp1 = "";
            if(appName1.contains("remote")) {
                remoteIp1 = appName1.split(":")[3];
            }
            String remoteIp2 = "";
            if(appName2.contains("remote")) {
                remoteIp2 = appName2.split(":")[3];
            }

            // 两个都是本地交换机
            if("".equals(remoteIp1) && "".equals(remoteIp2)) {
                // 创建两个虚拟交换机--前边都创建过交换机了，不需要再进行创建
                //ovsService.addBridge(appName1);
                //ovsService.addBridge(appName2);
                // 创建两个交换机之间的连接
                ovsService.linkOvs(appName1.substring(3), appName2.substring(3));
            }

            // 如果两个都是远程交换机
            else if(!"".equals(remoteIp1) && !"".equals(remoteIp2)) {
                // do nothing
            }
            // 如果其中之一是本地交换机，找到本地交换机，设置远端ip
            else {
                if("".equals(remoteIp1)) {
                    // appName2是远程的
                    ovsService.setVxlan(appName1.substring(3), remoteIp2);
                } else if("".equals(remoteIp2)) {
                    // appName1是远程的
                    ovsService.setVxlan(appName2.substring(3), remoteIp1);
                }
            }

        }
        // 如果其中一个是虚拟交换机，则创建虚拟交换机到容器的连接
        else if(flag == 1) {
            String ovsName = appName1.startsWith("br:") ? appName1.substring(3) : appName2.substring(3);
            String containerName = appName1.startsWith("br:") ? appName2 : appName1;

            // 使用ovsDocker接口创建连接
            // 获取全部的APP容器id
            Set<String> cids = clusterService.getContainerIdsByClusterId(clusterId, containerName);
            if(cids == null) {
                LOG.error("cannot find cids {}:{}", clusterId,containerName);
                throw new RuntimeException("cannot find cids");
            }
            cids.forEach( cid -> {
                // ip分配服务
                // TODO: 网段应该是和网络拓扑绑定到一起，需要重新进行设计
                String ip = ipService.assignIpString(clusterId, containerName,cid,"10.10.1.0/24");
                // ovsDockerService.addPort(ovsName, "eth1", containerName, "ip");
                ovsDockerService.addPort(ovsName, "eth1", cid, ip);
            });
        }
        // 这里非常特殊，只有1的时候才中断容器和容器之间的网络
        else {
            // 这里先不配置逻辑拓扑
            /*// throw new RuntimeException("Physical topology does not support c2c link.");
            // step1: 获取容器id
            Set<String> app1s = clusterService.getContainerIdsByClusterId(clusterId, appName1);
            Set<String> app2s = clusterService.getContainerIdsByClusterId(clusterId, appName2);

            // step:3 下发drop流表
            app1s.forEach(c -> {
                // 获取source ip
                String sourceIp = ipService.assignIpString("10.10.1.0/24", c);
                app2s.forEach( c1 -> {
                    String destinationIp = ipService.assignIpString("10.10.1.0/24", c1);
                    iptablesService.dropTraffic(c, sourceIp, destinationIp);
                    iptablesService.dropTraffic(c1, destinationIp, sourceIp);
                });
            });*/
        }
    }

    /**
     * 将从container1中的流量转发到container2中
     * @param containerId1
     * @param containerId2
     * @param port
     * @return
     */
    @Override
    public boolean forwardTraffic(String containerId1, String containerId2, int port) {
        // 查询到container1的ip地址
        String ip1 = ipService.getContainerIp(containerId1);
        // 查询到container2的ip地址
        String ip2 = ipService.getContainerIp(containerId2);
        // 在container1中设置转发
        return iptablesService.forwardTraffic(containerId1, ip1, port, ip2, port);
    }

    @Override
    public NetworkTopology getTopology(Long clusterId) {
        if(!clustersTopo.containsKey(clusterId)) {
            return null;
        }
        return clustersTopo.get(clusterId);
    }

    /**
     * 为新加入的容器创建网络
     * @param clusterId
     * @param appName
     * @param containerId
     */
    @Override
    public void createLinkForNewer(Long clusterId, String appName, String containerId) {
        // 找到topo
        if(!clustersTopo.containsKey(clusterId)) {
            LOG.warn("clusterId[" + clusterId + "] 拓扑不存在！");
            return;
        }
        NetworkTopology networkTopology = clustersTopo.get(clusterId);
        String[] appNames = networkTopology.getAppNames();
        // 判断appName的坐标
        int index = 0;
        for (String name : appNames) {
            if(name.equals(appName)) {
                break;
            }
            index++;
        }
        if(index == appNames.length) {
            LOG.warn("appName[" + appName + "]不存在！");
            return;
        }

        // 遍历第topo[index]行,根据连接关系创建网络
        int[][] topology = networkTopology.getTopology();
        for (int i = 0; i < topology[index].length; i++) {
            if(topology[i][index] == 1) {
                // 创建连接
                // 一端已经确定是容器
                if(appNames[i].startsWith("br:")) {
                    String ovsName = appNames[i].substring(3);
                    // 添加和交换机的网络链接
                    String ip = ipService.assignIpString(clusterId, appName,containerId,"10.10.1.0/24");
                    // ovsDockerService.addPort(ovsName, "eth1", containerName, "ip");
                    ovsDockerService.addPort(ovsName, "eth1", containerId, ip);
                }
                // 容器跟容器不能相连， 容器跟远端交换机也不能相连
            }
        }
    }


    // 调整网络拓扑，这里限定条件为，网络节点不改变
    @Override
    public String modifyTopology(long clusterId, NetworkTopology topology) {
        // 先进行校验
        if(topology == null) {
            return "topoloy不能为空";
        }

        // 获取clusterId的网络拓扑
        if(!clustersTopo.containsKey(clusterId)) {
            return "该集群没有设置topo";
        }

        NetworkTopology origTopo = clustersTopo.get(clusterId);
        // 校验topoId是否一致
        if(origTopo.getTopologyId() != topology.getTopologyId()) {
            return "不是相同的网络拓扑，无法更改";
        }

        // 校验成员名称是否相同
        for (int i = 0; i < origTopo.getAppNames().length; i++) {
            if(!origTopo.getAppNames()[i].equals(topology.getAppNames()[i])) {
                return "App名称必须一致";
            }
        }

        int[][] origTopoArr = origTopo.getTopology();
        int[][] newTopoArr = topology.getTopology();
        // 校验topo是否一致
        if((origTopoArr.length != newTopoArr.length)
                || (origTopoArr[0].length != newTopoArr[0].length)) {
            return "拓扑大小不一致";
        }

        // 更新网络拓扑,目前只校验下三角
        for (int i = 1; i < newTopoArr.length; i++) {
            for (int j = 0; j < i; j++) {
                if(origTopoArr[i][j] == 0 && newTopoArr[i][j] == 1) {
                    // 创建新的连接
                    addLink(clusterId, origTopo.getAppNames()[i], origTopo.getAppNames()[j]);
                }
                else if(origTopoArr[i][j] == 1 && newTopoArr[i][j] == 0) {
                    // 删除旧的连接
                    delLink(clusterId, origTopo.getAppNames()[i], origTopo.getAppNames()[j]);
                }
            }
        }
        clustersTopo.put(clusterId, topology);

        return "success";
    }

    /**
     * 删除两个节点之间的连接
     * @param clusterId
     * @param appName1
     * @param appName2
     */
    public void delLink(long clusterId, String appName1, String appName2) {
        int flag = 0;
        // 校验 参数是否合法
        // step1: 判断appNames中是否存在ovs虚拟交换机
        if(appName1.startsWith("br:")) {
            // flag计数器加1
            flag++;
        }

        if(appName2.startsWith("br:")) {
            flag++;
        }
        // 如果两个都是虚拟交换机，则需要创建交换机连接
        if(flag == 2) {
            // 校验是否是远端的交换机
            // 远端交换机输入样例：br:remote:testBr:192.168.31.111
            String remoteIp1 = "";
            if (appName1.contains("remote")) {
                remoteIp1 = appName1.split(":")[3];
            }
            String remoteIp2 = "";
            if (appName2.contains("remote")) {
                remoteIp2 = appName2.split(":")[3];
            }

            // 两个都是本地交换机
            if ("".equals(remoteIp1) && "".equals(remoteIp2)) {
                // 删除两个交换机之间的连接
                ovsService.delOvs(appName1.substring(3), appName2.substring(3));
            }

            // 如果两个都是远程交换机
            else if (!"".equals(remoteIp1) && !"".equals(remoteIp2)) {
                // do nothing
            }
            // 如果其中之一是本地交换机，找到本地交换机，设置远端ip
            else {
                if ("".equals(remoteIp1)) {
                    // appName2是远程的
                    ovsService.delVxlan(appName1.substring(3));
                } else if ("".equals(remoteIp2)) {
                    // appName1是远程的
                    ovsService.delVxlan(appName2.substring(3));
                }
            }
        }
        // 如果其中一个是虚拟交换机，则创建虚拟交换机到容器的连接
        else if(flag == 1) {
            String ovsName = appName1.startsWith("br:") ? appName1.substring(3) : appName2.substring(3);
            String containerName = appName1.startsWith("br:") ? appName2 : appName1;

            // 使用ovsDocker删除端口
            // 获取全部的APP容器id
            Set<String> cids = clusterService.getContainerIdsByClusterId(clusterId, containerName);
            cids.forEach( cid -> {
                // 获取ip，理论上应该在将该ip放入ip池中
                // TODO: 网段应该是和网络拓扑绑定到一起，需要重新进行设计
                // String ip = ipService.assignIpString("10.10.1.0/24", cid);
                ovsDockerService.delPort(ovsName, "eth1", cid);
            });
        }
        else {
            // 这里不配置逻辑拓扑
            // step1 先获取两个容器的id iptablesUtils.delIptable(dockerContainer.getId(), ChainType.INPUT, "172.0.17.3", "", "DROP");
            // step2 再获取两个容器的ip地址
            // step3 设置a->b的流向
            // step4 设置b->a的流向
            // throw new RuntimeException("Physical topology does not support c2c link.");
            // step1: 获取容器id
            /*Set<String> app1s = clusterService.getContainerIdsByClusterId(clusterId, appName1);
            Set<String> app2s = clusterService.getContainerIdsByClusterId(clusterId, appName2);

            // step:3 下发drop流表
            app1s.forEach(c -> {
                // 获取source ip
                String sourceIp = ipService.assignIpString("10.10.1.0/24", c);
                app2s.forEach( c1 -> {
                    String destinationIp = ipService.assignIpString("10.10.1.0/24", c1);
                    iptablesService.cancelDropTraffic(c, sourceIp, destinationIp);
                    iptablesService.cancelDropTraffic(c1, destinationIp, sourceIp);
                });
            });*/
        }
    }

    @Override
    public void removeTopoByCluterId(long clusterId) {
        if(!clustersTopo.containsKey(clusterId)) {
            return;
        }
        NetworkTopology topo = clustersTopo.get(clusterId);
        setZeroTopo(topo);
        // 先将全部网络调整为0
        modifyTopology(clusterId, topo);
        String[] appNames = clustersTopo.get(clusterId).getAppNames();
        for (String appName : appNames) {
            if(appName.contains("br") && !appName.contains("remote")) {
                ovsService.delBridge(appName.substring(3));
            }
        }
    }

    private void setZeroTopo(NetworkTopology topo) {
        int[][] topology = topo.getTopology();
        for (int i = 0; i < topology.length; i++) {
            for (int j = 0; j < topology.length ; j++) {
                topology[i][j] = 0;
            }
        }
    }

    @Override
    public void dropDockerNetwork(long clusterId, String appName, List<String> ipList) {
        // 找到全部的appName的容器id
        Set<String> cids = clusterService.getContainerIdsByClusterId(clusterId, appName);
        cids.forEach(cid -> {
            ipList.forEach( dstIp -> {
                iptablesService.dropTraffic(cid, ipService.getContainerIp(cid), dstIp);
            });
        });
    }

    @Override
    public void cancelDropDockerNetwork(long clusterId, String appName, List<String> ipList) {
        Set<String> cids = clusterService.getContainerIdsByClusterId(clusterId, appName);
        cids.forEach(cid -> {
            ipList.forEach( dstIp -> {
                iptablesService.cancelDropTraffic(cid, ipService.getContainerIp(cid), dstIp);
            });
        });
    }
}
